/*
 * FTNANDC024 NAND driver
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/partitions.h>
#include <linux/vmalloc.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/ktime.h>
#include <linux/scatterlist.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/mtd/bbm.h>
#include <mach/board-a369.h>
#include <mach/ftscu010.h>
#include <mach/ftdmac020.h>
#include <mach/dma-a369.h>

#include "ftnandc024v2.1_nand.h"
#include "ftnandc024v2.1_nand_cntr.h"
#include "ftnandc024v2.1_nand_gen.h"

#ifdef CONFIG_MTD_NAND_FTNANDC024
static int startchn = CONFIG_FTNANDC024_START_CHANNEL;
#else
static int startchn = 0;
module_param(startchn, int, 0644);
#endif

static struct ftnandc024_nandchip_attr nand_attr[] = {
	/* 
 	 * Manufacturer ID, spare size, ECC bits, ECC base shift, 
 	 * ECC for spare, Block Boundary, Protect Spare, legacy flash
 	 * */
	{"Micron 29F16G08MAA", 
          218, 8, 9, 4, 128, 1, LEGACY_FLASH},	/* 4K MLC */
	{"Samsung K9F4G08U0A", 
          64, 2, 9, 4, 64, 1, LEGACY_FLASH},	/* 2K SLC */
	{"Hynix HY27US08561A", 
          16, 3, 9, 1, 32, 1, LEGACY_FLASH},	/* 512B SLC */
	{"Toshiba TH58NVG5D2ETA20", 
          376, 24, 10, 4, 128, 1, LEGACY_FLASH},	/* 8K MLC */
	{"Toshiba TH58NVG7D2GTA20", 
          640, 40, 10, 4, 256, 1, LEGACY_FLASH},	/* 8K MLC */
	{"Samsung K9HDGD8X5M", 
          512, 24, 10, 4, 128, 1, TOGGLE1},		/* 8K TOGGLE MLC */
	{"Micron 29F32G08CBABB", 
          224, 8, 9, 4, 256, 1, ONFI2},			/* 4K ONFI MLC */
	{"Samsung K9LBG08U0M", 
          128, 4, 9, 4, 128, 1, LEGACY_FLASH},	/* 4K MLC */
	{"Toshiba TC58NVG4T2ETA00", 
          376, 24, 10, 4, 256, 1, LEGACY_FLASH},	/* 8K TLC */
	{"Toshiba TC58NVG6DCJTA00", 
          1280, 40, 10, 4, 256, 1, LEGACY_FLASH},	/* 16K MLC */
	{"Samsung K9GCGY8S0A", 
          640, 40, 10, 24, 128, 1, TOGGLE2},	/* 16K MLC */
	{"Toshiba TH58TEG7DCJBA4C", 
          1280, 40, 10, 40, 256, 1, TOGGLE2},	/* 16K MLC */
	{"Samsung K9ABGD8U0B", 
          1024, 60, 10, 60, 256, 1, TOGGLE1},	/* 8K TLC */
};

#ifdef CONFIG_MTD_CMDLINE_PARTS
static const char *part_probes[] = { "cmdlinepart", NULL };
#endif

/* Note: The unit of tWPST/tRPST/tWPRE/tRPRE field of ftnandc024_chip_timing is ns.
 *
 * tWH, tCH, tCLH, tALH, tCALH, tWP, tREH, tCR, tRSTO, tREAID,
 * tREA, tRP, tWB, tRB, tWHR, tWHR2, tRHW, tRR, tAR, tRC
 * tADL, tRHZ, tCCS, tCS, tCS2, tCLS, tCLR, tALS, tCALS, tCAL2, tCRES, tCDQSS, tDBS, tCWAW, tWPRE,
 * tRPRE, tWPST, tRPST, tWPSTH, tRPSTH, tDQSHZ, tDQSCK, tCAD, tDSL
 * tDSH, tDQSL, tDQSH, tDQSD, tCKWR, tWRCK, tCK, tCALS2, tDQSRE, tWPRE2, tRPRE2, tCEH
 
 */
#if defined (CONFIG_FTNANDC024_MICRON_29F16G08MAA)
static struct ftnandc024_chip_timing chip_timing = {
	7, 5, 5, 5, 0, 10, 10, 0, 0, 0,
	20, 12, 100, 0, 60, 0, 100, 20, 10, 0,
	70, 100, 70, 15, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 12, 10, 10, 0, 0,
	20, 12, 100, 0, 200, 0, 30, 20, 10, 0,
	0, 60, 0, 8, 0, 30, 10, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9F4G08U0A)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 12, 10, 0, 0, 0,
	20, 12, 100, 0, 60, 0, 100, 20, 10, 0,
	70, 100, 0, 20, 0, 12, 10, 12, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_HYNIX_HY27US08561A)
static struct ftnandc024_chip_timing chip_timing = {
	15, 10, 10, 10, 0, 25, 15, 0, 0, 0,
	30, 25, 100, 100, 60, 0, 0, 20, 10, 0,
	0, 30, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG5D2ETA20)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 12, 10, 10, 0, 0,
	20, 12, 100, 0, 180, 0, 30, 20, 10, 0,
	0, 60, 0, 8, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG7D2GTA20)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 12, 10, 10, 0, 0,
	20, 12, 100, 0, 200, 0, 30, 20, 10, 0,
	0, 60, 0, 8, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9HDGD8X5M)
static struct ftnandc024_chip_timing chip_timing = {
	11, 5, 0, 0, 5, 11, 7, 10, 0, 0,
	0, 7, 100, 0, 120, 300, 0, 20, 10, 0,
	300, 0, 0, 20, 0, 0, 10, 0, 15, 0, 0, 100, 0, 300, 15,
	15, 7, 33, 5, 5, 0, 0, 0, 0,
	0, 7, 7, 0, 0, 0, 0, 0, 25, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_MICRON_29F32G08CBABB)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 15, 10, 0, 0, 0,
	20, 15, 100, 0, 60, 0, 100, 20, 10, 0,
	100, 100, 200, 25, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

static struct ftnandc024_chip_timing sync_timing = {
	0, 5, 0, 0, 5, 0, 0, 0, 0, 0,
	0, 0, 100, 0, 60, 0, 100, 20, 0, 0,
	100, 0, 200, 25, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 20, 20, 25, 0,
	0, 0, 0, 20, 0, 20, 0, 0, 0, 0, 0, 0};

#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9LBG08U0M)
static struct ftnandc024_chip_timing chip_timing = {
	10, 5, 5, 5, 0, 12, 10, 20, 0, 0,
	20, 12, 100, 0, 60, 0, 100, 20, 10, 0,
	100, 100, 0, 20, 0, 12, 10, 12, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00)
static struct ftnandc024_chip_timing chip_timing = {
	11, 5, 5, 5, 0, 11, 7, 10, 0, 0,
	20, 10, 100, 0, 180, 300, 30, 20, 10, 0,
	300, 60, 0, 15, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9GCGY8S0A)
static struct ftnandc024_chip_timing chip_timing = {
	11, 5, 0, 0, 5, 11, 0, 10, 0, 0,
	0, 0, 100, 0, 120, 300, 0, 20, 10, 0,
	300, 0, 0, 25, 0, 0, 10, 0, 15, 0, 10, 100, 0, 300, 15,
	15, 7, 0, 25, 25, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, 0};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
static struct ftnandc024_chip_timing chip_timing = {
	11, 5, 0, 0, 5, 11, 7, 0, 0, 0,
	0, 0, 100, 0, 0, 0, 0, 20, 10, 0,
	300, 0, 0, 0, 0, 0, 0, 0, 15, 0, 100, 0, 0, 300, 15,
	15, 7, 0, 0, 25, 0, 0, 0, 0,
	0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0
	};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
/* Note: The unit of tWPST/tRPST/tWPRE/tRPRE field of ftnandc024_chip_timing is ns.
 *
 * tWH, tCH, tCLH, tALH, tCALH, tWP, tREH, tCR, tRSTO, tREAID,
 * tREA, tRP, tWB, tRB, tWHR, tWHR2, tRHW, tRR, tAR, tRC
 * tADL, tRHZ, tCCS, tCS, tCS2, tCLS, tCLR, tALS, tCALS, tCAL2, tCRES, tCDQSS, tDBS, tCWAW, tWPRE,
 * tRPRE, tWPST, tRPST, tWPSTH, tRPSTH, tDQSHZ, tDQSCK, tCAD, tDSL
 * tDSH, tDQSL, tDQSH, tDQSD, tCKWR, tWRCK, tCK, tCALS2, tDQSRE, tWPRE2, tRPRE2, tCEH
 
 */
static struct ftnandc024_chip_timing chip_timing = {
	11, 5, 0, 0, 5, 11, 0, 10, 0, 0,
	0, 0, 100, 0, 120, 300, 0, 20, 10, 0,
	300, 0, 0, 20, 30, 0, 10, 0, 15, 0, 10, 100, 0, 300, 15,
	15, 7, 0, 25, 25, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 0};
#endif

#if defined (CONFIG_FTNANDC024_MICRON_29F16G08MAA) ||\
	defined (CONFIG_FTNANDC024_SAMSUNG_K9HDGD8X5M) ||\
	defined (CONFIG_FTNANDC024_MICRON_29F32G08CBABB) ||\
	defined (CONFIG_FTNANDC024_SAMSUNG_K9LBG08U0M) ||\
	defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG7D2GTA20)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "DiskOnChip",
	 .offset = 128 * 1024 * 1024,
	 .size = 1536 * 1024 * 1024},
	{
	 .name = "Partition 1",
	 .offset = MTDPART_OFS_APPEND,
	 .size = 1536 * 1024 * 1024},
};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00) ||\
	defined (CONFIG_FTNANDC024_SAMSUNG_K9GCGY8S0A) ||\
	defined (CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "Partition 1",
	 .offset = 128 * 1024 * 1024,
	 .size =  (uint64_t)((uint64_t)3584 * (uint64_t)1024 * (uint64_t)1024)},
	{
	 .name = "Partition 2",
	 .offset = MTDPART_OFS_APPEND,
	 .size =  (uint64_t)((uint64_t)3584 * (uint64_t)1024 * (uint64_t)1024)},
};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "Partition 0",
	 .offset = 3 * 1024 * 1024,
	 .size = (long long)((long long)4089 *(long long)1024 * (long long)1024)},
		
};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "Partition 0",
	 .offset = 3 * 1024 * 1024,
	 .size = 2040 * 1024 * 1024},	
};
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG5D2ETA20)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "DiskOnChip",
	 .offset = 16 * 1024 * 1024,
	 .size = 896 * 1024 * 1024},
	{
	 .name = "Partition 1",
	 .offset = MTDPART_OFS_APPEND,
	 .size = 896 * 1024 * 1024},		
};
#elif defined (CONFIG_FTNANDC024_HYNIX_HY27US08561A)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "DiskOnChip",
	 .offset = 0,
	 .size = 16 * 1024 * 1024},
	{
	 .name = "Partition 1",
	 .offset = MTDPART_OFS_APPEND,
	 .size = 16 * 1024 * 1024},
};
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9F4G08U0A)
static struct mtd_partition ftnandc024_partition_info[] = {
	{
	 .name = "DiskOnChip",
	 .offset = 16 * 1024 * 1024,
	 .size = 192 * 1024 * 1024},
	{
	 .name = "Partition 1",
	 .offset = MTDPART_OFS_APPEND,
	 .size = 192 * 1024 * 1024},
};
#endif
static struct resource ftnandc024_resource[] = {
	[0] = {
	       .start = 0xC4000000,	/* Register Base address */
	       .end = 0xC400FFFF,
	       .flags = IORESOURCE_MEM,
	       },
	[1] = {
	       .start = 0xC2000000,	/* BMC buffer or Data Port access */
	       .end = 0xC2007FFF,
	       .flags = IORESOURCE_MEM,
	       }	
};


static struct nand_ecclayout nand_hw_eccoob = {
	.eccbytes = 0,
	.eccpos = {0},
	.oobfree = {
		    {.offset = 0,
		     .length = 64}}
};

static uint8_t ftnandc024_bbt_pattern[] = { 'B', 'b', 't', '0' };
static uint8_t ftnandc024_mirror_pattern[] = { '1', 't', 'b', 'B' };

static struct nand_bbt_descr ftnandc024_bbt_mirror_descr = {
	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
	    | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
	.offs = 0,
	.len = 4,
	.veroffs = 4,
	.maxblocks = 4,
	.pattern = ftnandc024_mirror_pattern
};

static struct nand_bbt_descr ftnandc024_bbt_main_descr = {
	.options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE
	    | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP,
	.offs = 0,
	.len = 4,
	.veroffs = 4,
	.maxblocks = 4,
	.pattern = ftnandc024_bbt_pattern
};

static uint8_t ftnandc024_scan_ff_pattern[] = { 0xff, 0xff, 0xff, 0xff };

static struct nand_bbt_descr ftnandc024_largepage_flashbased = {
	.options = NAND_BBT_SCAN2NDPAGE,
	.offs = 0,
	.len = 4,
	.pattern = ftnandc024_scan_ff_pattern
};

static void ftnandc024_set_default_warmup_cyc(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	char val;
	
	val = readb(data->io_base + MEM_ATTR_SET2);
	val &= (char)(~0xFF);
	writeb(val, data->io_base + MEM_ATTR_SET2); 
}

/* The unit of Hclk is MHz, and the unit of Time is ns.
 * We desire to calculate N to satisfy N*(1/Hclk) > Time given Hclk and Time
 * ==> N > Time * Hclk
 * ==> N > Time * 10e(-9) * Hclk *10e(6)        --> take the order out
 * ==> N > Time * Hclk * 10e(-3)
 * ==> N > Time * Hclk / 1000
 * ==> N = (Time * Hclk + 999) / 1000
 */
static void ftnandc024_calc_timing(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int tWH, tWP, tREH, tRES, tBSY, tBUF1;
	int tBUF2, tBUF3, tBUF4, tPRE, tRLAT, t1;
	int tPST, tPSTH, tWRCK;
	int i, toggle_offset = 0;
	struct ftnandc024_chip_timing *p;
	u32 CLK, FtCK, timing[4];

	CLK = FREQ_SETTING / 1000000;
	
	tWH = tWP = tREH = tRES =  0;
	tRLAT = tBSY = t1 = 0;
	tBUF4 = tBUF3 = tBUF2 = tBUF1 = 0;
	tPRE = tPST = tPSTH = tWRCK = 0;
#if defined (CONFIG_FTNANDC024_MICRON_29F32G08CBABB)
	if (data->flash_type == ONFI2)
		p = &sync_timing;
	else
#endif
		p = &chip_timing;
	
	if(data->flash_type == LEGACY_FLASH) {
		// tWH = max(tWH, tCH, tCLH, tALH)
		tWH = max_4(p->tWH, p->tCH, (int)p->tCLH, (int)p->tALH);
		tWH = (tWH * CLK) / 1000;
		// tWP = tWP
		tWP = (p->tWP * CLK) / 1000;
		// tREH = tREH
		tREH = (p->tREH * CLK) / 1000;
		// tRES = max(tREA, tRSTO, tREAID)
		tRES = max_3(p->tREA, p->tRSTO, (int)p->tREAID);
		tRES = (tRES * CLK) / 1000;
		// tRLAT < (tRES + tREH) + 2
		tRLAT = tRES + tREH;
		// t1 = max(tCS, tCLS, tALS) - tWP
		t1 = max_3(p->tCS, p->tCLS, (int)p->tALS) - p->tWP;
		if (t1 < 0)
			t1 = 0;
		else 
			t1 = (t1 * CLK) / 1000;
		// tPSTH(EBI setup time) = max(tCS, tCLS, tALS)
		tPSTH = max_3(p->tCS, p->tCLS, (int)p->tALS);
		tPSTH = (tPSTH * CLK) / 1000;
		// tWRCK(EBI hold time) = max(tRHZ, tREH)
		tWRCK = max_2(p->tRHZ, p->tREH);
		tWRCK = (tWRCK * CLK) / 1000;
	}
	else if(data->flash_type == ONFI2) {
		// tWP = tCAD
		tWP = (p->tCAD * CLK) / 1000;
		
		// Fill this field with value N, FTck = mem_clk/2(N + 1)
		// Note:mem_clk is same as core_clk. Here, we'd like to 
		// assign 30MHz to FTck.
		tRES = 0;
		FtCK = CLK / ( 2 * (tRES + 1));
		
		// Increase p->tCK by one, is for the fraction which 
		// cannot store in the variable, Integer type.
		p->tCK = 1000 / FtCK + 1;
		#if 0 	
		// The vfp calculation isn't supported in kernel code.
		p->tWPRE = 1.5 * p->tCK;
		p->tWPST = 1.5 * p->tCK;
		p->tDQSL = 0.6 * p->tCK;
		p->tDQSH = 0.6 * p->tCK;
		#else
		p->tWPRE = 2 * p->tCK;
		p->tWPST = 2 * p->tCK;
		p->tDQSL = 1 * p->tCK;
		p->tDQSH = 1 * p->tCK;
		#endif

		p->tCKWR = (p->tDQSCK + p->tCK) / p->tCK;
		if(p->tDQSCK % p->tCK !=0)
			p->tCKWR += 1;
		
		t1 = (p->tCS * CLK) / 1000;

		tPRE = 2;	// Assign 2 due to p->tWPRE is 1.5*p->tCK
		tPST = 2;	// Assign 2 due to p->tWPST is 1.5*p->tCK
		tPSTH = ((p->tDQSHZ * FtCK) / 1000) + 1;
		tWRCK = (p->tWRCK * CLK) /1000;
		
	}
	else if(data->flash_type == ONFI3) {
	}
	else if(data->flash_type == TOGGLE1) {
		// tWH = max(tWH, tCH, tCALH)
		tWH = max_3(p->tWH, p->tCH, (int)p->tCALH);
		tWH = (tWH * CLK) / 1000;
		// tWP = tWP
		tWP = (p->tWP * CLK) / 1000;
		// tREH = tCR
		tREH = (p->tCR * CLK) / 1000;
		// tRES = max(tRP, tREH)
		tRES = max_2(p->tRP, p->tREH);
		tRES = (tRES * CLK) / 1000;
		// t1 = max(tCALS2-tWP, tCWAW)
		t1 = max_2((p->tCALS2 - p->tWP), p->tCWAW);
		t1 = (t1 * CLK) / 1000;
		// tPRE = max(tWPRE, 2*tRPRE, tRPRE+tDQSRE) + 1
		tPRE = max_3((int)p->tWPRE, (int)(p->tRPRE << 1),\
					 (p->tRPRE + p->tDQSRE));
		tPRE = (tPRE * CLK) / 1000;
		tPRE +=1;
		// tPST = max(tWPST, tRPST) + 1
		tPST = max_2(p->tWPST, p->tRPST);
		tPST = (tPST * CLK) / 1000;
		tPST +=1;
		// tPSTH = max(tWPSTH, tRPSTH)
		tPSTH = max_2(p->tWPSTH, p->tRPSTH);
		tPSTH = (tPSTH * CLK) / 1000;
		// tWRCK = max(tDSL, tDSH, tDQSL, tDQSH)
		tWRCK = max_4(p->tDSL, p->tDSH, (int)p->tDQSL, (int)p->tDQSH);
		tWRCK = (tWRCK * CLK) / 1000;
	}
	else if(data->flash_type == TOGGLE2) {
		p->tRC = 1000 / CLK;
		p->tRPST = p->tDQSRE + (p->tRC >> 1);
		p->tRP = p->tREH = p->tDQSH = p->tDQSL = (p->tRC >> 1);
		
		tWH = max_2(p->tCH, p->tCALH);
		tWH = (tWH * CLK) / 1000;

		tWP = (p->tWP * CLK) / 1000;

		tRES = max_2(p->tRP, p->tREH);
		tRES = (tRES * CLK) / 1000;

		t1 = max_2((p->tCALS2 - p->tWP), p->tCWAW);
		t1 = (t1 * CLK) / 1000;

		tPRE = max_2(max_3(p->tWPRE, p->tWPRE2, p->tRPRE),
						max_3(p->tRPRE2, p->tCS, p->tCS2));
		tPRE = (tPRE * CLK) / 1000;
		tPRE+= 1;

		tPST = max_2(p->tWPST, p->tRPST);
		tPST = (tPST * CLK) / 1000;
		tPST+= 1;

		tPSTH = max_2(p->tWPSTH, p->tRPSTH);
		tPSTH = (tPSTH * CLK) / 1000;

		tWRCK = max_4(p->tDSL, p->tDSH, p->tDQSL, p->tDQSH);
		tWRCK = (tWRCK * CLK) / 1000;
	}

	// tBSY = max(tWB, tRB), min value = 1
	tBSY = max_2(p->tWB, p->tRB);
	tBSY = (tBSY * CLK) / 1000;
	if(tBSY < 1)
		tBSY = 1;
	// tBUF1 = max(tADL, tCCS)
	tBUF1 = max_2(p->tADL, (int)p->tCCS);
	tBUF1 = (tBUF1 * CLK) / 1000;
	// tBUF2 = max(tAR, tRR, tCLR, tCDQSS, tCRES, tCALS, tCALS2, tDBS)
	tBUF2 = max_2(max_4(p->tAR, p->tRR, (int)p->tCLR, (int)p->tCDQSS),
				max_4((int)p->tCRES, (int)p->tCALS, (int)p->tCALS2, (int)p->tDBS));
	tBUF2 = (tBUF2 * CLK) / 1000;
	// tBUF3 = max(tRHW, tRHZ, tDQSHZ)
	tBUF3 = max_3(p->tRHW, p->tRHZ, (int)p->tDQSHZ);
	tBUF3 = (tBUF3 * CLK) / 1000;
	// tBUF4 = max(tWHR, tWHR2)
	tBUF4 = max_2((int)p->tWHR, p->tWHR2);
	tBUF4 = (tBUF4 * CLK) / 1000;

	// For FPGA, we use the looser AC timing
	if(data->flash_type == TOGGLE1 || data->flash_type == TOGGLE2) {
		
		toggle_offset = 3;
		tREH += toggle_offset;
		tRES += toggle_offset;
		tWH +=toggle_offset;
		tWP +=toggle_offset;
		t1  +=toggle_offset;
		tBSY+=toggle_offset;
		tBUF1+=toggle_offset;
		tBUF2+=toggle_offset;
		tBUF3+=toggle_offset;
		tBUF4+=toggle_offset;
		tWRCK+=toggle_offset;
		tPSTH+=toggle_offset;
		tPST+=toggle_offset;
		tPRE+=toggle_offset;		
	}

	timing[0] = (tWH << 24) | (tWP << 16) | (tREH << 8) | tRES;
	timing[1] = (tRLAT << 16) | (tBSY << 8) | t1;
	timing[2] = (tBUF4 << 24) | (tBUF3 << 16) | (tBUF2 << 8) | tBUF1;
	timing[3] = (tPRE << 28) | (tPST << 24) | (tPSTH << 16) | tWRCK;

	for (i = 0;i < MAX_CHANNEL;i++) {
		writel(timing[0], data->io_base + FL_AC_TIMING0(i));
		writel(timing[1], data->io_base + FL_AC_TIMING1(i));
		writel(timing[2], data->io_base + FL_AC_TIMING2(i));
		writel(timing[3], data->io_base + FL_AC_TIMING3(i));
	}	
	
	DBGLEVEL2(ftnandc024_dbg("AC Timing 0:0x%08x\n", timing[0]));
	DBGLEVEL2(ftnandc024_dbg("AC Timing 1:0x%08x\n", timing[1]));
	DBGLEVEL2(ftnandc024_dbg("AC Timing 2:0x%08x\n", timing[2]));
	DBGLEVEL2(ftnandc024_dbg("AC Timing 3:0x%08x\n", timing[3]));
}

static void ftnandc024_onfi_set_feature(struct mtd_info *mtd, int chn, int ce, int val)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;

	/* 0x11 means Timing mode 1 and Synchronous DDR */
	writel(val, data->io_base + SPARE_SRAM + (chn << 5));

	/* 0x1 is Timing mode feature address */
	cmd_f.row_cycle = ROW_ADDR_2CYCLE;
	cmd_f.col_cycle = COL_ADDR_1CYCLE;
	cmd_f.cq1 = 0x1;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = 0;
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(LEGACY_FLASH) |\
			CMD_START_CE(ce) | CMD_BYTE_MODE | CMD_SPARE_NUM(4) |\
			CMD_INDEX(ONFI_FIXFLOW_SETFEATURE);

	ftnandc024_issue_cmd(mtd, &cmd_f);

	ftnandc024_nand_wait(mtd, chip);

}

static void ftnandc024_read_raw_id(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	u8 id_size = 5;

#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	data->cur_chan = 1;
	data->sel_chip = 0;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00)
	data->cur_chan = 0;
	data->sel_chip = 3;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C) ||\
	defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
	data->cur_chan = 0;
	data->sel_chip = 1;
#else
	data->cur_chan = 0;
	data->sel_chip = 0;
#endif

	// Set the flash to Legacy mode, in advance.
	if(data->flash_type == ONFI2 || data->flash_type == ONFI3) {
		ftnandc024_onfi_set_feature(mtd, data->cur_chan, data->sel_chip, 0x00);
	}
	
	// Issue the RESET cmd
	cmd_f.cq1 = 0;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = 0;
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(LEGACY_FLASH) |\
			CMD_START_CE(data->sel_chip) | CMD_INDEX(FIXFLOW_RESET);

	ftnandc024_issue_cmd(mtd, &cmd_f);
	
	ftnandc024_nand_wait(mtd, chip);
	
	
	// Issue the READID cmd
	cmd_f.row_cycle = ROW_ADDR_1CYCLE;
	cmd_f.col_cycle = COL_ADDR_1CYCLE;
	cmd_f.cq1 = 0;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = CMD_COUNT(1);
	cmd_f.cq4 = CMD_FLASH_TYPE(LEGACY_FLASH) | CMD_COMPLETE_EN |\
			CMD_INDEX(FIXFLOW_READID) | CMD_START_CE(data->sel_chip) |\
			CMD_BYTE_MODE | CMD_SPARE_NUM(id_size);
	
	ftnandc024_issue_cmd(mtd, &cmd_f);	

	ftnandc024_nand_wait(mtd, chip);
	
	memcpy(data->flash_raw_id, data->io_base + SPARE_SRAM + (data->cur_chan << 5) , id_size);

	DBGLEVEL2(ftnandc024_dbg("ID@(ch:%d, ce:%d):0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", 
					data->cur_chan, data->sel_chip, data->flash_raw_id[0], 
					data->flash_raw_id[1], data->flash_raw_id[2], 
					data->flash_raw_id[3], data->flash_raw_id[4]));
}

static void ftnandc024_calibrate_dqs_delay(struct mtd_info *mtd, int type)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	int i, max_dqs_delay = 0;
	int id_size = 5;
	int id_size_ddr = (id_size << 1);
	u8 *p, *golden_p;
	u8 dqs_lower_bound, dqs_upper_bound, state;
	u32 val;

	dqs_lower_bound = dqs_upper_bound = 0;
	p = kmalloc(id_size, GFP_KERNEL);
	golden_p = kmalloc(id_size, GFP_KERNEL);
	 	
	if(type == ONFI2) {
		/* Extent the data from SDR to DDR. 
		   Ex. If "0xaa, 0xbb, 0xcc, 0xdd, 0xee" is in SDR, 
		          "0xaa, 0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, 0xee" is in DDR(ONFI).
		*/
		for(i = 0; i< id_size; i++) {
			*(golden_p + (i << 1) + 0) = *(data->flash_raw_id + i);
			*(golden_p + (i << 1) + 1) = *(data->flash_raw_id + i);
		}
		DBGLEVEL2(ftnandc024_dbg("Golden ID:0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", 
					*golden_p, *(golden_p+1), *(golden_p+2), 
					*(golden_p+3), *(golden_p+4), *(golden_p+5)));
		max_dqs_delay = 20;
	}
	else if(type == TOGGLE1 || TOGGLE2) {
		/* Extent the data from SDR to DDR. 
		   Ex. If "0xaa, 0xbb, 0xcc, 0xdd, 0xee" is in SDR, 
		          "0xaa, 0xbb, 0xbb, 0xcc, 0xcc, 0xdd, 0xdd, 0xee, 0xee" is in DDR(TOGGLE).
		*/
		for(i = 0; i< id_size; i++) {
			*(golden_p + (i << 1) + 0) = *(data->flash_raw_id + i);
			*(golden_p + (i << 1) + 1) = *(data->flash_raw_id + i);
		}
		golden_p ++;
		
		DBGLEVEL2(ftnandc024_dbg("Golden ID:0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", 
					*golden_p, *(golden_p+1), *(golden_p+2), 
					*(golden_p+3), *(golden_p+4), *(golden_p+5)));
		max_dqs_delay = 18;
	}
	else {
		printk("%s:Type:%d isn't allowed\n", __func__, type);
		goto out;
	}	
		

	state = 0;
	for(i = 0; i <= max_dqs_delay; i++) {
		// setting the dqs delay before READID.
		writel(i, data->io_base + DQS_DELAY);
		memset(p, 0, id_size_ddr);
		
		// Issuing the READID
		cmd_f.row_cycle = ROW_ADDR_1CYCLE;
		cmd_f.col_cycle = COL_ADDR_1CYCLE;
		cmd_f.cq1 = 0;
		cmd_f.cq2 = 0;
		cmd_f.cq3 = CMD_COUNT(1);
		cmd_f.cq4 = CMD_FLASH_TYPE(type) | CMD_COMPLETE_EN |\
				CMD_INDEX(FIXFLOW_READID) | CMD_BYTE_MODE |\
				CMD_START_CE(data->sel_chip) | CMD_SPARE_NUM(id_size_ddr);
		
		ftnandc024_issue_cmd(mtd, &cmd_f);
		
		ftnandc024_nand_wait(mtd, chip);
		
			
		if(type == ONFI2) {
			memcpy(p, data->io_base + SPARE_SRAM + (data->cur_chan<< 5), id_size_ddr);
			if(state == 0 && memcmp(golden_p, p, id_size_ddr) == 0) {
				dqs_lower_bound = i;
				state = 1;
			}
			else if(state == 1 && memcmp(golden_p, p, id_size_ddr) != 0){
				dqs_upper_bound = i - 1;
				break;
			}
		}
		else if(type == TOGGLE1 || type == TOGGLE2) {
			memcpy(p, data->io_base + SPARE_SRAM + (data->cur_chan<< 5), id_size_ddr-1);
			
			if(state == 0 && memcmp(golden_p, p, (id_size_ddr - 1)) == 0) {
				dqs_lower_bound = i;
				state = 1;
			}
			else if(state == 1 && memcmp(golden_p, p, (id_size_ddr - 1)) != 0){
				dqs_upper_bound = (i - 1);
				break;
			}

		}
		DBGLEVEL2(ftnandc024_dbg("===============================================\n"));
		DBGLEVEL2(ftnandc024_dbg("ID       :0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", 
							*p, *(p+1), *(p+2), *(p+3), 
							*(p+4), *(p+5), *(p+6), *(p+7),
							*(p+8) ));
		DBGLEVEL2(ftnandc024_dbg("Golden ID:0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", 
							*golden_p, *(golden_p+1), *(golden_p+2), *(golden_p+3), 
							*(golden_p+4), *(golden_p+5),*(golden_p+6), *(golden_p+7), 
							*(golden_p+8) ));
		DBGLEVEL2(ftnandc024_dbg("===============================================\n"));	
	}
	// Prevent the dqs_upper_bound is zero when ID still accuracy on the max dqs delay
	if(i == max_dqs_delay + 1)
		dqs_upper_bound = max_dqs_delay;
		
	printk("Upper:%d & Lower:%d for DQS, then Middle:%d\n", 
		dqs_upper_bound, dqs_lower_bound, ((dqs_upper_bound + dqs_lower_bound) >> 1));
	// Setting the middle dqs delay
	val = readl(data->io_base + DQS_DELAY);
	val &= ~0x1F;
	val |= (((dqs_lower_bound + dqs_upper_bound) >> 1) & 0x1F);
	writel(val, data->io_base + DQS_DELAY);
out:
	kfree(p);
	kfree(golden_p);

}

static uint32_t ftnandc024_onfi_get_feature(struct mtd_info *mtd, int chn, int ce, int type)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	u32 val;

	/* 0x1 is Timing mode feature address */
	cmd_f.row_cycle = ROW_ADDR_2CYCLE;
	cmd_f.col_cycle = COL_ADDR_1CYCLE;
	cmd_f.cq1 = 0x1;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = 0;
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(type) |\
			CMD_START_CE(ce) | CMD_BYTE_MODE | CMD_SPARE_NUM(4) |\
			CMD_INDEX(ONFI_FIXFLOW_GETFEATURE);

	ftnandc024_issue_cmd(mtd, &cmd_f);

	ftnandc024_nand_wait(mtd, chip);

	val = readl(data->io_base + SPARE_SRAM + (chn << 5));

	return val;
}

static void ftnandc024_t2_get_feature(struct mtd_info *mtd, int chn, int ce, u8 *buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	u8  i;

	/* 0x1 is Timing mode feature address */
	cmd_f.row_cycle = ROW_ADDR_1CYCLE;
	cmd_f.cq1 = 0x2;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = 0;
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) |\
			CMD_START_CE(ce) | CMD_BYTE_MODE | CMD_SPARE_NUM(8) |\
			CMD_INDEX(ONFI_FIXFLOW_GETFEATURE);
	
	ftnandc024_issue_cmd(mtd, &cmd_f);

	ftnandc024_nand_wait(mtd, chip);

	for (i = 0; i< 4; i++)
		*(buf + i) = readb(data->io_base + SPARE_SRAM + (chn << 5) + (i << 1));

	printk("T2 Get feature:0x%08x\n", *((int *)(buf)));

}

static void ftnandc024_t2_set_feature(struct mtd_info *mtd, int chn, int ce, u8 *buf)
{
	u8 i;
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;

	for (i = 0; i< 4; i ++) {
		writel(*(buf + i), data->io_base + SPARE_SRAM + (chn << 5) + (i << 1));
		writel(*(buf + i), data->io_base + SPARE_SRAM + (chn << 5) + (i << 1) + 1);
	}

	cmd_f.row_cycle = ROW_ADDR_1CYCLE;
	cmd_f.cq1 = 0x2;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = 0;
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) |\
			CMD_START_CE(ce) | CMD_BYTE_MODE | CMD_SPARE_NUM(8) |\
			CMD_INDEX(ONFI_FIXFLOW_SETFEATURE);

	ftnandc024_issue_cmd(mtd, &cmd_f);

	ftnandc024_nand_wait(mtd, chip);

}


static int ftnandc024_onfi_sync(struct mtd_info *mtd)
{
	struct nand_chip *p = mtd->priv;
	struct ftnandc024_nand_data *data = p->priv;
	u32 val;
	int ret = -1;

	ftnandc024_nand_select_chip(mtd, 0);
	val = ftnandc024_onfi_get_feature(mtd, data->cur_chan, data->sel_chip, 0);
	printk("onfi feature for Ch %d, CE %d: 0x%x\n", data->cur_chan, data->sel_chip, val);

	ftnandc024_onfi_set_feature(mtd, data->cur_chan, data->sel_chip, 0x11);
	
	val = ftnandc024_onfi_get_feature(mtd, data->cur_chan, data->sel_chip, ONFI2);
	printk("onfi feature for Ch %d, CE %d: 0x%x\n", data->cur_chan, data->sel_chip, val);
	if (val != 0x1111) {
		goto out;
	}
	ret = 0;

out:
	return ret;
}

static int ftnandc024_warmup_cycle(struct mtd_info *mtd, u8 wr_cyc, u8 rd_cyc) {

	u8 param[4];
	int val;
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;

	// Reset the setting to make sure the accuracy of Get/Set feature
	val = readl(data->io_base + MEM_ATTR_SET2);
	val &= ~(0xFF);
	writel(val, data->io_base + MEM_ATTR_SET2);

	ftnandc024_nand_select_chip(mtd, 0);

	// Set feature
	param[0] = param[1] = param[2] = param[3] = 0;
	ftnandc024_t2_set_feature(mtd, data->cur_chan, data->sel_chip, param);
		
	// Get feature
	ftnandc024_t2_get_feature(mtd, data->cur_chan, data->sel_chip, param);

	// Set feature
	param[1] = ((wr_cyc & 0x3) << 4) | (rd_cyc & 0x3);
	ftnandc024_t2_set_feature(mtd, data->cur_chan, data->sel_chip, param);

	// Get feature
	ftnandc024_t2_get_feature(mtd, data->cur_chan, data->sel_chip, param);
	
	// Set the controller 
	val = readl(data->io_base + MEM_ATTR_SET2);
	val &= ~(0xFF);
	val |= (((rd_cyc & 0x3) << 4) | (wr_cyc & 0x3));
	writel(val, data->io_base + MEM_ATTR_SET2);
	
	return 0;
} 


static int ftnandc024_available_oob(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;

	int ret = 0;
	int consume_byte, eccbyte, eccbyte_spare;
	int available_spare;
	int sector_num = (mtd->writesize >> data->eccbasft);
	
	if (data->useecc < 0)
		goto out;
	if (data->protect_spare != 0)
		data->protect_spare = 1;
	else
		data->protect_spare = 0;

	eccbyte = (data->useecc * 14) / 8;
	if (((data->useecc * 14) % 8) != 0)
		eccbyte++;

	consume_byte = (eccbyte * sector_num);
	if (data->protect_spare == 1) { 
		
		eccbyte_spare = (data->useecc_spare * 14) / 8;
		if (((data->useecc_spare * 14) % 8) != 0)
			eccbyte_spare++;
		consume_byte += eccbyte_spare;
	}
	available_spare = data->spare - consume_byte;	

	DBGLEVEL2(ftnandc024_dbg(
		"mtd->erasesize:%d, mtd->writesize:%d, chip->block_boundary:%d", 
		mtd->erasesize, mtd->writesize, chip->block_boundary));
	DBGLEVEL2(ftnandc024_dbg(
		"page num:%d, data->eccbasft:%d, protect_spare:%d, spare:%d", 
		mtd->erasesize/mtd->writesize,data->eccbasft, data->protect_spare, 
		data->spare));
	DBGLEVEL2(ftnandc024_dbg(
		"consume_byte:%d, eccbyte:%d, eccbytes(spare):%d, useecc:%d",
		consume_byte, eccbyte, eccbyte_spare, data->useecc));
	
	/*----------------------------------------------------------
	 * YAFFS require 16 bytes OOB without ECC, 28 bytes with
	 * ECC enable.
	 * BBT require 5 bytes for Bad Block Table marker.
	 */
	if (available_spare >= 4) {
		if(available_spare >= 32) {
			ret = 32;
		}
		else if(available_spare >= 16) {
			ret = 16;
		}
		else if(available_spare >= 8) {
			ret = 8;
		}
		else if(available_spare >= 4) {
			ret = 4;
		}
		printk(KERN_INFO "Available OOB is %d byte, but we use %d bytes in page mode.\n", available_spare, ret);
	} else {
		printk(KERN_INFO "Not enough OOB, try to reduce ECC correction bits.\n");
		printk(KERN_INFO "(Currently ECC setting for Data:%d)\n", data->useecc);
		printk(KERN_INFO "(Currently ECC setting for Spare:%d)\n", data->useecc_spare);
	}
out:
	return ret;
}

static uint8_t ftnandc024_read_byte(struct mtd_info *mtd)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	uint32_t lv;
	uint8_t b = 0;

	switch (data->cur_cmd) {
	case NAND_CMD_READID:
		lv = readl(data->io_base + SPARE_SRAM + (data->cur_chan << 5));
		b = (lv >> data->byte_ofs) & 0xFF;
		data->byte_ofs += 8;
		if (data->byte_ofs == 32)
			data->byte_ofs = 0;
		break;
	case NAND_CMD_STATUS:
		lv = readl(data->io_base + READ_STATUS0);
		lv = lv >> (data->cur_chan * 8);
		b = (lv & 0xFF);
		break;
	}
	return b;
}

/*static int ftnandc024_nand_read_oob_lp(struct mtd_info *mtd, u_char * buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int status = 0, i, ecc_original_setting, generic_original_setting, val;
	int real_pg, empty;
	
	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	int real_blk_nm, real_off;
	real_blk_nm = data->page_addr / (mtd->erasesize/mtd->writesize);
	real_off = data->page_addr % (mtd->erasesize/mtd->writesize);
	real_pg = (real_blk_nm * chip->block_boundary) + real_off;
	#else
	real_pg = data->page_addr;
	#endif
	
	DBGLEVEL2(ftnandc024_dbg(
		"read_oob: ch = %d, ce = %d, page = 0x%x, real: 0x%x, size = %d\n", 
		data->cur_chan, data->sel_chip, data->page_addr, real_pg, mtd->writesize));

	if(!rd_oob(mtd, real_pg, chip->oob_poi)) {
		if(data->cmd_status & CMD_ECC_FAIL_ON_SPARE) {
			// Store the original setting
			ecc_original_setting = readl(data->io_base + ECC_CONTROL); 
			generic_original_setting = readl(data->io_base + GENERAL_SETTING);
			// Disable the ECC engine & HW-Scramble, temporarily.
			val = readl(data->io_base + ECC_CONTROL);
			val = val & ~(ECC_EN(0xFF));
			writel(val, data->io_base + ECC_CONTROL);
			val = readl(data->io_base + GENERAL_SETTING);
			val &= ~DATA_SCRAMBLER;
			writel(val, data->io_base + GENERAL_SETTING);
			
			if(!rd_oob(mtd, real_pg, chip->oob_poi)) {	
				empty = 1;
				for (i = 0; i < mtd->oobsize; i++) {
					if (*(buf + i) != 0xFF) {
						printk(KERN_ERR "ECC err for spare(Read oob) @");
						printk(KERN_ERR	"ch:%d ce:%d page0x%x real:0x%x\n",
							data->cur_chan, data->sel_chip, data->page_addr, real_pg);
						mtd->ecc_stats.failed++;
						status = -1;
						empty = 0;
						break;
					}
				}
				if (empty == 1)
					DBGLEVEL2(ftnandc024_dbg("Spare real 0xFF"));
			}			
			// Restore the ecc original setting & generic original setting.
			writel(ecc_original_setting, data->io_base + ECC_CONTROL);
			writel(generic_original_setting, data->io_base + GENERAL_SETTING);
		}
	}

	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;
}*/

/*static int ftnandc024_nand_read_oob_sp(struct mtd_info *mtd, u_char * buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int i, status = 0;
	int val, ecc_original_setting, empty;

	DBGLEVEL2(ftnandc024_dbg("smallread_oob: ch = %d, ce = %d, page = 0x%x, size = %d\n", 
				data->cur_chan, data->sel_chip, data->page_addr, mtd->writesize));

	if(!rd_oob_sp(mtd, data->page_addr, buf)) {
		if(data->cmd_status & CMD_ECC_FAIL_ON_SPARE) {
			// Store the original setting
			ecc_original_setting = readl(data->io_base + ECC_CONTROL); 
			// Disable the ECC engine & HW-Scramble, temporarily.
			val = readl(data->io_base + ECC_CONTROL);
			val = val & ~(ECC_EN(0xFF));
			writel(val, data->io_base + ECC_CONTROL);
			
			if(!rd_oob_sp(mtd, data->page_addr, buf)) {
				empty = 1;
				for (i = 0; i < mtd->oobsize; i++) {             
					if (*(buf + i) != 0xFF) {                
						printk(KERN_ERR "ECC err for spare(Read oob) @ page0x%x\n",
								data->page_addr);
						mtd->ecc_stats.failed++;
						status = -1;
						empty = 0;
						break;           
					}                      
				}
				if(empty == 1)	
					DBGLEVEL2(ftnandc024_dbg("Spare real 0xFF"));
			}
			// Restore the ecc original setting & generic original setting.
			writel(ecc_original_setting, data->io_base + ECC_CONTROL);
		}
	}
	

	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;
}*/

/*static int ftnandc024_nand_read_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page,
					int sndcmd)
{
	struct ftnandc024_nand_data *data = chip->priv;

	data->page_addr = page;

	return data->read_oob(mtd, chip->oob_poi);
}*/

/*static int ftnandc024_nand_read_page_lp(struct mtd_info *mtd, u_char *buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int status = 0, chk_data_0xFF, chk_spare_0xFF;
	int i, ecc_original_setting, generic_original_setting, val;
	int real_pg;
	u8  empty;
	u32 *lbuf;
	#ifdef Read_Retry
	int need_readretry = 0, chg_param_times = 0;
	int parameter_group_num;
	#endif
	
	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	int real_blk_nm, real_off;
	real_blk_nm = data->page_addr / (mtd->erasesize/mtd->writesize);
	real_off = data->page_addr % (mtd->erasesize/mtd->writesize);
	real_pg = (real_blk_nm * chip->block_boundary) + real_off;
	#else
	real_pg = data->page_addr;
	#endif	

	DBGLEVEL2(ftnandc024_dbg
		("r: ch = %d, ce = %d, page = 0x%x, real = 0x%x, size = %d, data->column = %d\n", 
		data->cur_chan, data->sel_chip, data->page_addr, real_pg, mtd->writesize, data->column));

#ifdef Read_Retry
retry:
#endif
	chk_data_0xFF = chk_spare_0xFF = 0;
		
	if(!rd_pg_w_oob(mtd, real_pg, buf, chip->oob_poi)) {
		
		if (data->cmd_status & 
			(CMD_ECC_FAIL_ON_DATA | CMD_ECC_FAIL_ON_SPARE)) {
			// Store the original setting
			ecc_original_setting = readl(data->io_base + ECC_CONTROL); 
			generic_original_setting = readl(data->io_base + GENERAL_SETTING);
			// Disable the ECC engine & HW-Scramble, temporarily.
			val = readl(data->io_base + ECC_CONTROL);
			val = val & ~(ECC_EN(0xFF));
			writel(val, data->io_base + ECC_CONTROL);
			val = readl(data->io_base + GENERAL_SETTING);
			val &= ~DATA_SCRAMBLER;
			writel(val, data->io_base + GENERAL_SETTING);

			if( data->cmd_status == (CMD_ECC_FAIL_ON_DATA | CMD_ECC_FAIL_ON_SPARE) ) {
			
				if(!rd_pg_w_oob(mtd, real_pg, buf, chip->oob_poi)) {
					chk_data_0xFF = chk_spare_0xFF = 1;
				}	
			}
			else if(data->cmd_status == CMD_ECC_FAIL_ON_DATA) {
				if(!rd_pg(mtd, real_pg, buf)) {
					chk_data_0xFF = 1;
				}	
			}
			else if(data->cmd_status == CMD_ECC_FAIL_ON_SPARE) {
				if(!rd_oob(mtd, real_pg, chip->oob_poi)) {
					chk_spare_0xFF = 1;
				}	
			}
		
			// Restore the ecc original setting & generic original setting.
			writel(ecc_original_setting, data->io_base + ECC_CONTROL);
			writel(generic_original_setting, data->io_base + GENERAL_SETTING);

			if(chk_data_0xFF == 1) {
				empty = 1;
				lbuf = (int *)buf;
				for (i = 0; i < (mtd->writesize >> 2); i++) {
					if (*(lbuf + i) != 0xFFFFFFFF) {
						#ifdef Read_Retry
						need_readretry = 1;
						#else
						printk(KERN_ERR "ECC err @ page0x%x real:0x%x\n", 
							data->page_addr, real_pg);
						mtd->ecc_stats.failed++;
						status = -1;
						#endif
						empty = 0;
						break;	
					}
				}
				if(empty == 1)
					DBGLEVEL2(ftnandc024_dbg("Data Real 0xFF\n"));
			}
			if(chk_spare_0xFF == 1) {
				if(!ftnandc024_check_bad_spare(mtd, data->page_addr))
					empty = 1;
				else {
					#ifdef Read_Retry
					need_readretry = 1;
					#else
					printk(KERN_ERR "ECC err for spare(Read page) @");
					printk(KERN_ERR	"ch:%d ce:%d page0x%x real:0x%x\n",
						data->cur_chan, data->sel_chip, data->page_addr, real_pg);
					mtd->ecc_stats.failed++;
					status = -1;
					#endif
					empty = 0;
				}
				if(empty == 1)
					DBGLEVEL2(ftnandc024_dbg("Spare Real 0xFF\n"));
			}
			#ifdef Read_Retry
			if(need_readretry == 1) {
				// Reset the variable	
				need_readretry = 0;
				#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00) ||\
					defined(CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
				if(chg_param_times < MAX_CHG_PARAM_TIMES) {
					ftnandc024_set_param(mtd, chg_param_times);
					chg_param_times++;
					goto retry;
				}
				else {
					mtd->ecc_stats.failed++;
					status = -1;
				}
			
				#elif defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
				if(chg_param_times < 26) {
					parameter_group_num = data->current_parameter_group;
					parameter_group_num = (parameter_group_num + 1) % 26;
					printk("ECC err @ Ch:%d Ce:%d Page:0x%x Real:0x%x\n",
							data->cur_chan, data->sel_chip, data->page_addr, real_pg);
					printk("Parameter is from %d to %d\n", 
							data->current_parameter_group, parameter_group_num);
					ftnandc024_set_sam_param_data( mtd, parameter_group_num); 	
					goto retry;
				}
				else {
					mtd->ecc_stats.failed ++;
					status = -1;
					printk("Restore the default offs\n");
					ftnandc024_set_sam_param_data(mtd, 0);
				}	
				#endif
			}
			#endif

		}
	}
	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00) ||\
		defined(CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
	if(chg_param_times > 0) {
		ftnandc024_terminate_set_param(mtd);	
	}
	#elif defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
	if(chg_param_times > 0) {
		printk("Restore the default offs\n");
		ftnandc024_set_sam_param_data(mtd, 0);
	}
	#endif
	
	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/


/*static int ftnandc024_nand_read_page_sp(struct mtd_info *mtd, u_char * buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	int status = 0;
	int i, ecc_original_setting, val;
	int chk_data_0xFF, chk_spare_0xFF, empty;
	u8 *p;
	u32 *lbuf;

	DBGLEVEL2(ftnandc024_dbg("smallr: ch = %d, ce = %d, page = 0x%x, size = %d, data->column = %d\n", 
			data->cur_chan, data->sel_chip, data->page_addr, mtd->writesize, data->column));

	chk_data_0xFF = chk_spare_0xFF = 0;
	if(!rd_pg_w_oob_sp(mtd, data->page_addr, buf, chip->oob_poi)) {
		if(data->cmd_status & (CMD_ECC_FAIL_ON_DATA | CMD_ECC_FAIL_ON_SPARE)) {
			// Store the original setting
			ecc_original_setting = readl(data->io_base + ECC_CONTROL); 
			// Disable the ECC engine & HW-Scramble, temporarily.
			val = readl(data->io_base + ECC_CONTROL);
			val = val & ~(ECC_EN(0xFF));
			writel(val, data->io_base + ECC_CONTROL);
			
			if(data->cmd_status == (CMD_ECC_FAIL_ON_DATA | CMD_ECC_FAIL_ON_SPARE)) {
				if(!rd_pg_w_oob_sp(mtd, data->page_addr, buf, chip->oob_poi)) {
					chk_data_0xFF = chk_spare_0xFF = 1;
				}	
			}
			else if(data->cmd_status == CMD_ECC_FAIL_ON_DATA) {
				if(!rd_pg_sp(mtd, data->page_addr, buf)) {
					chk_data_0xFF = 1;
				}
			}
			else if(data->cmd_status == CMD_ECC_FAIL_ON_SPARE) {
				if(!rd_oob_sp(mtd, data->page_addr, chip->oob_poi)) {
					chk_spare_0xFF = 1;		
				}
			}
			// Restore the ecc original setting & generic original setting.
			writel(ecc_original_setting, data->io_base + ECC_CONTROL);
			
			if(chk_data_0xFF == 1) {
				lbuf = (u32 *)buf;
				empty = 1;
				for (i = 0; i < (mtd->writesize >> 2); i++) {
					if (*(lbuf + i) != 0xFFFFFFFF) {
						printk(KERN_ERR "ECC err @ page0x%x\n", data->page_addr); 
						ftnandc024_regdump(mtd);
						mtd->ecc_stats.failed++;
						status = -1;
						empty = 0;
						break;
					}
				}
				if (empty == 1)
					DBGLEVEL2(ftnandc024_dbg("Data Real 0xFF\n"));
			}
			if(chk_spare_0xFF == 1) {
				p = chip->oob_poi;
				empty = 1;
				for (i = 0; i < mtd->oobsize; i++) {
					if (*(p + i) != 0xFF) {
						printk(KERN_ERR"ECC err for spare(Read page) @ page0x%x\n", data->page_addr);
						mtd->ecc_stats.failed++;
						status = -1;
						empty = 0;
						break;
					}
				}
				if (empty == 1)
					DBGLEVEL2(ftnandc024_dbg("Spare Real 0xFF\n"));
			}
		}
	}

	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/

/*static int ftnandc024_nand_read_page(struct mtd_info *mtd, struct nand_chip *chip, uint8_t * buf, int page)
{
	struct ftnandc024_nand_data *data = chip->priv;

	data->buf = (u32 *) buf;

	return data->read_page(mtd, buf);
}*/

/*static int ftnandc024_nand_write_oob_lp(struct mtd_info *mtd, const u_char * buf, int len)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	int status = 0, real_pg;

	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	int real_blk_nm, real_off;
	real_blk_nm = data->page_addr / (mtd->erasesize/mtd->writesize);
	real_off = data->page_addr % (mtd->erasesize/mtd->writesize);
	real_pg = (real_blk_nm * chip->block_boundary) + real_off;
	#else
	real_pg = data->page_addr;
	#endif	
	
	DBGLEVEL2(ftnandc024_dbg(
		"write_oob: ch = %d, ce = %d, page = 0x%x, real page:0x%x, sz = %d, oobsz = %d\n", 
		data->cur_chan, data->sel_chip, data->page_addr, real_pg, mtd->writesize, mtd->oobsize));

	memcpy(data->io_base + SPARE_SRAM + (data->cur_chan << 5), buf, mtd->oobsize);
	
	cmd_f.row_cycle = ROW_ADDR_3CYCLE;
	cmd_f.col_cycle = COL_ADDR_2CYCLE;
	cmd_f.cq1 = real_pg;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = CMD_COUNT(1);
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) |\
			CMD_START_CE(data->sel_chip) | CMD_SPARE_NUM(data->spare) |\
			CMD_INDEX(LARGE_FIXFLOW_WRITEOOB);
		
	status = ftnandc024_issue_cmd(mtd, &cmd_f);
	if (status < 0)
		goto out;

	if (ftnandc024_nand_wait(mtd, chip) == NAND_STATUS_FAIL) {
		//return -1;
	}
out:
	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/

/*static int ftnandc024_nand_write_oob_sp(struct mtd_info *mtd, const u_char * buf, int len)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	int status = 0;
	
	DBGLEVEL2(ftnandc024_dbg("smallwrite_oob: ch = %d, ce = %d, page = 0x%x, size = %d\n", 
				data->cur_chan, data->sel_chip, data->page_addr, mtd->writesize));
	
	memcpy(data->io_base + SPARE_SRAM + (data->cur_chan << 5), buf, len);

	cmd_f.row_cycle = ROW_ADDR_2CYCLE;
	cmd_f.col_cycle = COL_ADDR_1CYCLE;
	cmd_f.cq1 = data->page_addr;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = CMD_COUNT(1);
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) |\
			CMD_START_CE(data->sel_chip) | CMD_SPARE_NUM(data->spare) |\
			CMD_INDEX(SMALL_FIXFLOW_WRITEOOB);

	status = ftnandc024_issue_cmd(mtd, &cmd_f);
	if (status < 0)
		goto out;

	if (ftnandc024_nand_wait(mtd, chip) == NAND_STATUS_FAIL) {
		//return -1;
	}
out:
	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/

/*static int ftnandc024_nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, int page)
{
	struct ftnandc024_nand_data *data = chip->priv;

	DBGLEVEL2(ftnandc024_dbg("write oob only to page = 0x%x\n", page));
	data->page_addr = page;

	return data->write_oob(mtd, chip->oob_poi, mtd->oobsize);
}*/

/*static int ftnandc024_nand_write_page_lp(struct mtd_info *mtd, const uint8_t * buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	u8 *p, w_wo_spare = 1;
	#ifndef CONFIG_FTNANDC024_USE_AHBDMA
	u32 *lbuf;
	#endif
	int real_pg;
	int i, status = 0;

	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	int real_blk_nm, real_off;
	real_blk_nm = data->page_addr / (mtd->erasesize/mtd->writesize);
	real_off = data->page_addr % (mtd->erasesize/mtd->writesize);
	real_pg = (real_blk_nm * chip->block_boundary) + real_off;
	#else
	real_pg = data->page_addr;
	#endif	
	
	DBGLEVEL2(ftnandc024_dbg (
		"w: ch = %d, ce = %d, page = 0x%x, real page:0x%x size = %d, data->column = %d\n", 
		data->cur_chan, data->sel_chip,  data->page_addr, real_pg, mtd->writesize, data->column));
	
	p = chip->oob_poi;
	for(i = 0; i < mtd->oobsize; i++) {
		if( *( p + i) != 0xFF) {
			w_wo_spare = 0;
			break;
		}
	}
	
	cmd_f.row_cycle = ROW_ADDR_3CYCLE;
	cmd_f.col_cycle = COL_ADDR_2CYCLE;
	cmd_f.cq1 = real_pg;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = CMD_COUNT(mtd->writesize >> data->eccbasft) | 
			(data->column & 0xFF);
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) | \
			CMD_START_CE(data->sel_chip) | CMD_SPARE_NUM(data->spare);
#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	cmd_f.cq4 |= CMD_DMA_HANDSHAKE_EN;
#endif
	if(w_wo_spare == 0) {
		memcpy((data->io_base + SPARE_SRAM + (data->cur_chan << 5)), p, mtd->oobsize);
		cmd_f.cq4 |= CMD_INDEX(LARGE_PAGEWRITE_W_SPARE);
	}
	else {
		cmd_f.cq4 |= CMD_INDEX(LARGE_PAGEWRITE);
	}

	status = ftnandc024_issue_cmd(mtd, &cmd_f);
	if (status < 0)
		goto out;
	
#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	if(ftnandc024_nand_dma_wr(mtd, buf)) {
		goto out;
	}
#else
	lbuf = (u32 *) buf;
	for (i = 0; i < mtd->writesize; i += 4)
		*(volatile unsigned *)(data->chip.IO_ADDR_R) = *lbuf++;
#endif
	
	if (ftnandc024_nand_wait(mtd, chip) == NAND_STATUS_FAIL) {
		status = -1;
		printk("FAILED\n");
	}	
out:	
	return 0;	
	
}*/

/*static int ftnandc024_nand_write_page_sp(struct mtd_info *mtd, const uint8_t * buf)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;	
	struct cmd_feature cmd_f;
	int i;
	int status = 0;
	u8 *p, w_wo_spare = 1;
	int progflow_buf[3];
	#ifndef CONFIG_FTNANDC024_USE_AHBDMA
	u32 *lbuf;
	#endif

	DBGLEVEL2(ftnandc024_dbg ("smallw: ch = %d, ce = %d, page = 0x%x, size = %d, data->column = %d\n", 
				data->cur_chan, data->sel_chip,  data->page_addr, mtd->writesize, data->column));
	
	p = chip->oob_poi;
	for(i = 0; i < mtd->oobsize; i++) {
		if( *( p + i) != 0xFF) {
			w_wo_spare = 0;
			break;
		}
	}
		
	cmd_f.row_cycle = ROW_ADDR_2CYCLE;
	cmd_f.col_cycle = COL_ADDR_1CYCLE;
	cmd_f.cq1 = data->page_addr;
	cmd_f.cq2 = 0;
	cmd_f.cq3 = CMD_COUNT(1) | (data->column / mtd->writesize);
	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type) |\
			CMD_START_CE(data->sel_chip) | CMD_SPARE_NUM(data->spare);
	#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	cmd_f.cq4 |= CMD_DMA_HANDSHAKE_EN;
	#endif

	if(w_wo_spare == 0) {
		memcpy((data->io_base + SPARE_SRAM + (data->cur_chan << 5)), chip->oob_poi, mtd->oobsize);
		cmd_f.cq4 |= CMD_INDEX(SMALL_FIXFLOW_PAGEWRITE);
	}
	else {
		progflow_buf[0] = 0x41421D00;
		progflow_buf[1] = 0x66086460;
		progflow_buf[2] = 0x000067C7;
		ftnandc024_fill_prog_flow(mtd, progflow_buf, 10);
		cmd_f.cq4 |= CMD_PROM_FLOW | CMD_INDEX(0x0);
	}

	status = ftnandc024_issue_cmd(mtd, &cmd_f);
	if (status < 0)
		goto out;
	
	#ifdef CONFIG_FTNANDC024_USE_AHBDMA	
	if(ftnandc024_nand_dma_wr(mtd, buf)) {
		goto out;
	}
	#else
	lbuf = (u32 *) buf;
	for (i = 0; i < mtd->writesize; i += 4)
		*(volatile unsigned *)(data->chip.IO_ADDR_R) = *lbuf++;
	#endif
	if (ftnandc024_nand_wait(mtd, chip) == NAND_STATUS_FAIL) {
		
	}
out:
	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/

/*static int ftnandc024_nand_write_page(struct mtd_info *mtd, struct nand_chip *chip,
				      const uint8_t * buf, int page, int cached, int raw)
{
	struct nand_chip *p = mtd->priv;
	struct ftnandc024_nand_data *data = p->priv;
	int status = 0;
#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
	u8 *vbuf;
	int i;
#endif
	DBGLEVEL2(ftnandc024_dbg ("w_2: ch = %d, ce = %d, page = 0x%x, size = %d, data->column = %d\n", 
				data->cur_chan, data->sel_chip,  page, mtd->writesize, data->column));
	data->page_addr = page;
	status = data->write_page(mtd, buf);
	if (status < 0)
		goto out;

#ifdef CONFIG_MTD_NAND_VERIFY_WRITE
	vbuf = vmalloc(mtd->writesize + mtd->oobsize);
	memcpy(vbuf + mtd->writesize, chip->oob_poi, mtd->oobsize);

	status = data->read_page(mtd, vbuf);
	if (status < 0)
		goto out;
	for (i = 0; i < mtd->writesize; i++) {
		if (*(buf + i) != *(vbuf + i)) {
			printk(KERN_ERR "write verify failed at normal region..");
			goto out_free;
		}
	}
	for (i = 0; i < mtd->oobsize; i++) {
		if (*(chip->oob_poi + i) != *(vbuf + mtd->writesize + i)) {
			printk(KERN_ERR "write verify failed at oob region..\n");
			goto out_free;
		}
	}

out_free:
	vfree(vbuf);
#endif
out:
	// Returning the any value isn't allowed, except 0, -EBADMSG, or -EUCLEAN
	return 0;	
}*/

/*static void ftnandc024_nand_write_page_lowlevel(struct mtd_info *mtd,
						struct nand_chip *chip, const uint8_t * buf)
{
}*/

static void ftnandc024_nand_cmdfunc(struct mtd_info *mtd, unsigned command,
				    int column, int page_addr)
{
	struct nand_chip *chip = mtd->priv;
	struct ftnandc024_nand_data *data = chip->priv;
	struct cmd_feature cmd_f;
	int real_pg, cmd_sts;
	u8 id_size = 6;

	cmd_f.cq4 = CMD_COMPLETE_EN | CMD_FLASH_TYPE(data->flash_type);
	data->cur_cmd = command;
	if (page_addr != -1)
		data->page_addr = page_addr;
	if (column != -1)
		data->column = column;
	
	#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00) ||\
		defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
	int real_blk_nm, real_off;
	#else
	real_pg = data->page_addr;
	#endif		
	
	switch (command) {
	case NAND_CMD_READID:
		DBGLEVEL2(ftnandc024_dbg( "Read ID@(CH:%d, CE:%d)\n", data->cur_chan, data->sel_chip));	
		data->byte_ofs = 0;
		// ID size is doubled when the mode is DDR.
		if(data->flash_type == TOGGLE1 || data->flash_type == ONFI2 ||
		data->flash_type == TOGGLE2){
			id_size = (id_size << 1);
		}
		
		cmd_f.row_cycle = ROW_ADDR_1CYCLE;
		cmd_f.col_cycle = COL_ADDR_1CYCLE;
		cmd_f.cq1 = 0;
		cmd_f.cq2 = 0;
		cmd_f.cq3 = CMD_COUNT(1);
		cmd_f.cq4 |= CMD_START_CE(data->sel_chip) | CMD_BYTE_MODE |\
				CMD_SPARE_NUM(id_size) | CMD_INDEX(FIXFLOW_READID);
		
		cmd_sts = ftnandc024_issue_cmd(mtd, &cmd_f);
		if(!cmd_sts)
			ftnandc024_nand_wait(mtd, chip);
		else
			printk(KERN_ERR "Read ID err\n");

		break;
	case NAND_CMD_RESET:
		DBGLEVEL2(ftnandc024_dbg("Cmd Reset@(CH:%d, CE:%d)\n", data->cur_chan, data->sel_chip));
		
		cmd_f.cq1 = 0;
		cmd_f.cq2 = 0;
		cmd_f.cq3 = 0;
		cmd_f.cq4 |= CMD_START_CE(data->sel_chip);
		if (data->flash_type == ONFI2 || data->flash_type == ONFI3)
			cmd_f.cq4 |= CMD_INDEX(ONFI_FIXFLOW_SYNCRESET);
		else
			cmd_f.cq4 |= CMD_INDEX(FIXFLOW_RESET);

		cmd_sts = ftnandc024_issue_cmd(mtd, &cmd_f);
		if(!cmd_sts)
			ftnandc024_nand_wait(mtd, chip);
		else
			printk(KERN_ERR "Reset Flash err\n");

		break;
	case NAND_CMD_STATUS:
		DBGLEVEL2(ftnandc024_dbg( "Read Status\n"));
		
		cmd_f.cq1 = 0;
		cmd_f.cq2 = 0;
		cmd_f.cq3 = CMD_COUNT(1);
		cmd_f.cq4 |= CMD_START_CE(data->sel_chip) | CMD_INDEX(FIXFLOW_READSTATUS);

		cmd_sts = ftnandc024_issue_cmd(mtd, &cmd_f);
		if(!cmd_sts)
			ftnandc024_nand_wait(mtd, chip);
		else
			printk(KERN_ERR "Read Status err\n");

		break;
	case NAND_CMD_ERASE1:
		#if defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00) ||\
			defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
		real_blk_nm = data->page_addr / (mtd->erasesize/mtd->writesize);
		real_off = data->page_addr % (mtd->erasesize/mtd->writesize);
		real_pg = (real_blk_nm * chip->block_boundary) + real_off;
		#endif		
		DBGLEVEL2(ftnandc024_dbg(
			"Erase Page: 0x%x, Real:0x%x\n", data->page_addr, real_pg));
		
		cmd_f.cq1 = real_pg;
		cmd_f.cq2 = 0;
		cmd_f.cq3 = CMD_COUNT(1);
		cmd_f.cq4 |= CMD_START_CE(data->sel_chip) | CMD_SCALE(1);
		
		if (data->large_page) {
			cmd_f.row_cycle = ROW_ADDR_3CYCLE;
			cmd_f.col_cycle = COL_ADDR_2CYCLE;
			cmd_f.cq4 |= CMD_INDEX(LARGE_FIXFLOW_ERASE);
		} else {
			cmd_f.row_cycle = ROW_ADDR_2CYCLE;
			cmd_f.col_cycle = COL_ADDR_1CYCLE;
			cmd_f.cq4 |= CMD_INDEX(SMALL_FIXFLOW_ERASE);
		}
		
		/* Someone may be curious the following snippet that
		* ftnandc024_issue_cmd doesn't be followed by
		* ftnandc024_nand_wait.
		* Waiting cmd complete will be call on the mtd upper layer via
		* the registered data->chip.waitfunc.
		*/
		cmd_sts = ftnandc024_issue_cmd(mtd, &cmd_f);
		if(cmd_sts)
			printk(KERN_ERR "Erase block err\n");

		break;
	case NAND_CMD_ERASE2:
	case NAND_CMD_PAGEPROG:
	case NAND_CMD_SEQIN:
		break;
	}
}

void ftnandc024_nand_select_chip(struct mtd_info *mtd, int chip)
{
	struct nand_chip *p = mtd->priv;
	struct ftnandc024_nand_data *data = p->priv;
	int chn = 0;

	if (data->scan_state != 1) {
		while (chip != -1) {
			if (chip < data->valid_chip[chn]) {
				break;
			} else {
				chip = chip - data->valid_chip[chn];
				chn++;
			}
		}
		data->cur_chan = chn;
	}
#if defined(CONFIG_FTNANDC024_HYNIX_HY27US08561A)
	data->cur_chan = 0;
	data->sel_chip = 2;
#elif defined(CONFIG_FTNANDC024_SAMSUNG_K9F4G08U0A)
	data->cur_chan = 0; 
	data->sel_chip = 2;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TH58NVG7D2GTA20)
	data->cur_chan = 1; 
	data->sel_chip = 0;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	data->cur_chan = 1; 
	data->sel_chip = 0;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00)
	data->cur_chan = 0; 
	data->sel_chip = 3;
#elif defined(CONFIG_FTNANDC024_SAMSUNG_K9GCGY8S0A)
	data->cur_chan = 0; 
	data->sel_chip = 0;
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
	data->cur_chan = 0; 
	data->sel_chip = 1;
#elif defined(CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
	data->cur_chan = 0; 
	data->sel_chip = 1;
#else
	data->sel_chip = chip;
#endif
	DBGLEVEL2(ftnandc024_dbg("==>chan = %d, ce = %d\n", data->cur_chan, data->sel_chip));
}

/*
 * Probe for the NAND device.
 */
static int __init ftnandc024_nand_probe(struct platform_device *pdev)
{
	struct clk *clk;
	struct ftnandc024_nand_data *data;
	int res, chipnum;
	uint64_t size, max_sz = -1;
	int i, sel, type;
#if defined(CONFIG_FTNANDC024_USE_AHBDMA)
	struct dma_chan *dma_chan;
	dma_cap_mask_t mask;
#endif
	u32 val;

	struct mtd_partition *partitions = NULL;
	int num_partitions = 0;
	
	res = chipnum = size = type = 0;
	/* Allocate memory for the device structure (and zero it) */
	
	/* 
 	 * Initialize the parameter
 	 */
	data = ftnandc024_init_param();
	//data = kzalloc(sizeof(struct ftnandc024_nand_data), GFP_KERNEL);
	if (!data) {
		dev_err(&pdev->dev, "failed to allocate device structure.\n");
		res = -ENOMEM;
		goto out;
	}
	/* Setting the Ext-AHB in A369*/
	clk = clk_get(NULL, "extahb");
	clk_disable(clk);
	clk_set_rate(clk, 33000000);
	clk_enable(clk);
	clk_put(clk);
			
	data->io_base = ioremap(pdev->resource[0].start,
				pdev->resource[0].end - pdev->resource[0].start + 1);
	if (data->io_base == NULL) {
		dev_err(&pdev->dev, "ioremap failed for register.\n");
		res = -EIO;
		goto out_free_data;
	}
	DBGLEVEL2(ftnandc024_dbg("data->io_base:0x%08x", (unsigned int)data->io_base));
#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	data->chip.IO_ADDR_R = (void *)pdev->resource[1].start;
#else
	data->chip.IO_ADDR_R = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start + 1);
	if (data->chip.IO_ADDR_R == NULL) {
		dev_err(&pdev->dev, "ioremap failed for data port.\n");
		res = -EIO;
		goto out_no_ior;
	}
#endif	
	// Reset the HW
	// Note: We can't use the function of ftnandc024_soft_reset to reset the hw
	//       because the private data field of ftnandc024_nand_data is null.
	writel(1, data->io_base + GLOBAL_RESET);
	while (readl(data->io_base + GLOBAL_RESET)) ;

#if defined(CONFIG_FTNANDC024_HYNIX_HY27US08561A) ||\
	defined(CONFIG_FTNANDC024_SAMSUNG_K9F4G08U0A)	
	/* We disable scramble function on SLC because SLC 
	 * usually needs fewer ecc correction capability. 
	 * The fewer ecc correction capability, the more 
	 * probability of ecc misjudgement especially for 
	 * paged pattern(0xff) 
	 */
	// which is randomized by scrambler.
	writel(BUSY_RDY_LOC(6) | CMD_STS_LOC(0) | CE_NUM(2), data->io_base + GENERAL_SETTING);
#else
	writel(BUSY_RDY_LOC(6) | CMD_STS_LOC(0) | CE_NUM(2) | DATA_SCRAMBLER, data->io_base + GENERAL_SETTING);
#endif

	val = readl(data->io_base + AHB_SLAVEPORT_SIZE);
	val &= ~0xFFF0FF;
	val |= AHB_SLAVE_SPACE_32KB;
	for(i = 0; i < MAX_CHANNEL; i++)
		val |= AHB_PREFETCH(i);
	val |= AHB_PRERETCH_LEN(128);	
	writel(val, data->io_base + AHB_SLAVEPORT_SIZE);
#if defined(CONFIG_FTNANDC024_USE_AHBDMA)
	/* This must be same as the length of prefetch(128 words)*/
	data->slave.common.src_maxburst = data->slave.common.dst_maxburst = 128;
#endif

#if defined (CONFIG_FTNANDC024_MICRON_29F16G08MAA)
	sel = 0;
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9F4G08U0A)
	sel = 1;
#elif defined (CONFIG_FTNANDC024_HYNIX_HY27US08561A)
	sel = 2;
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG5D2ETA20)
	sel = 3;
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58NVG7D2GTA20)
	sel = 4;
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9HDGD8X5M)
	sel = 5;
#elif defined (CONFIG_FTNANDC024_MICRON_29F32G08CBABB)
	sel = 6;
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9LBG08U0M)
	sel = 7;
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG4T2ETA00)
	sel = 8;
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TC58NVG6DCJTA00)
	sel = 9;
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9GCGY8S0A)
	sel = 10;
#elif defined (CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
	sel = 11;
#elif defined (CONFIG_FTNANDC024_SAMSUNG_K9ABGD8U0B)
	sel = 12;
#else
	sel = -1;
#endif

	if (sel != -1) {
		printk(KERN_INFO "Use %s NAND chip...\n", nand_attr[sel].name);
		data->spare = nand_attr[sel].sparesize;
		data->useecc = nand_attr[sel].ecc;
		data->useecc_spare = nand_attr[sel].ecc_spare;
		data->eccbasft = nand_attr[sel].eccbaseshift;
		data->protect_spare = nand_attr[sel].crc;
		data->flash_type = nand_attr[sel].flash_type;

	} else {
		printk(KERN_INFO "Use Unknown type NAND chip...\n");
		goto out_no_ior;
	}
	
	data->chip.priv = data;
	data->mtd.priv = &data->chip;
	data->mtd.owner = THIS_MODULE;
	data->mtd.name = pdev->dev.init_name;
	data->dev = &pdev->dev;
	data->chip.IO_ADDR_W = data->chip.IO_ADDR_R;
	data->chip.select_chip = ftnandc024_nand_select_chip;
	data->chip.cmdfunc = ftnandc024_nand_cmdfunc;
	data->chip.read_byte = ftnandc024_read_byte;
	data->chip.write_page = ftnandc024_nand_write_page;
	data->chip.waitfunc = ftnandc024_nand_wait;
	data->chip.chip_delay = 0;
	//data->chip.options = NAND_USE_FLASH_BBT | NAND_NO_SUBPAGE_WRITE | NAND_OWN_BUFFERS;
	data->chip.options = NAND_NO_SUBPAGE_WRITE | NAND_OWN_BUFFERS;
	data->chip.bbt_options = NAND_BBT_USE_FLASH;
	data->chip.block_boundary = nand_attr[sel].block_boundary;
	platform_set_drvdata(pdev, data);
	
	// Set the default AC timing/Warmup cyc for ftnandc024. 
	// The register of AC timing/Warmup  keeps the value 
	// set before although the Global Reset is set. 
	ftnandc024_set_default_timing(&data->mtd);
	ftnandc024_set_default_warmup_cyc(&data->mtd);
	
	// Disable the scan state for ftnandc024_nand_select_chip
	data->scan_state = 0;

	// Read the raw id to calibrate the DQS delay for Sync. latching(DDR)
	ftnandc024_read_raw_id(&data->mtd);
	if(data->flash_type == TOGGLE1 || data->flash_type == TOGGLE2) {	
		ftnandc024_calc_timing(&data->mtd);	
		ftnandc024_calibrate_dqs_delay(&data->mtd, data->flash_type);	
	}

	/*--------------------------------------------------------
	 * ONFI flash must work in Asynch mode for READ ID command.
	 * Switch it back to Legacy.
	 */
	if (data->flash_type == ONFI2) {
		type = data->flash_type;
		data->flash_type = LEGACY_FLASH;
	}
	
	// Enable the scan state for ftnandc024_nand_select_chip
	data->scan_state = 1;
	// Reset the flash delay set before.
	if(data->flash_type == TOGGLE2)
		ftnandc024_warmup_cycle(&data->mtd, 0, 0);
	/* Scan to find existance of the device */
	for (i = startchn; i < MAX_CHANNEL; i++) {
		printk(KERN_INFO "Scan Channel %d...\n", i);
		data->cur_chan = i;
		if (!nand_scan_ident(&data->mtd, MAX_CE, NULL)) {
			if ((max_sz - size) > (data->mtd.size)
				&& ((chipnum + data->chip.numchips) <= NAND_MAX_CHIPS)) {
					data->valid_chip[i] = data->chip.numchips;
					chipnum += data->chip.numchips;
					size += (chipnum * data->chip.chipsize);
			} else {
					printk(KERN_INFO "Can not accept more flash chips.\n");
					break;
			}
		}
	}
	// Disable the scan-state for ftnandc024_nand_select_chip
	data->scan_state = 0;
	
	if (chipnum == 0) {
		res = -ENXIO;
		goto out_unset_drv;
	}
	
	data->chip.numchips = chipnum;
	data->mtd.size = size;

	data->chip.ecc.layout = &nand_hw_eccoob;
	data->chip.bbt_td = &ftnandc024_bbt_main_descr;
	data->chip.bbt_md = &ftnandc024_bbt_mirror_descr;
	data->chip.badblock_pattern = &ftnandc024_largepage_flashbased;

	if (data->spare < (data->mtd.writesize >> 5))
		data->spare = (data->mtd.writesize >> 5);

	val = readl(data->io_base + MEM_ATTR_SET);
	val &= ~(0x7 << 16);
	switch (data->mtd.writesize) {
	case 512:
		val |= PG_SZ_512;
		data->large_page = 0;
		break;
	case 2048:
		val |= PG_SZ_2K;
		data->large_page = 1;
		break;
	case 4096:
		val |= PG_SZ_4K;
		data->large_page = 1;
		break;
	case 8192:
		val |= PG_SZ_8K;
		data->large_page = 1;
		break;
	case 16384:
		val |= PG_SZ_16K;
		data->large_page = 1;
		break;
	}
	val &= ~(0x3FF << 2);
	val |= ((data->chip.block_boundary - 1) << 2);
	writel(val, data->io_base + MEM_ATTR_SET);
	
	val = readl(data->io_base + MEM_ATTR_SET2); 
	val &= ~(0x3FF << 16);
	val |=  VALID_PAGE((data->mtd.erasesize / data->mtd.writesize - 1));
	writel(val, data->io_base + MEM_ATTR_SET2);

	i = ftnandc024_available_oob(&data->mtd);
	if (likely(i >= 4)) {
		if (i > 32)
			data->mtd.oobsize = 32;
		else
			data->mtd.oobsize = i;
		data->chip.ecc.layout->oobfree[0].length = data->mtd.oobsize;
	} else {
		res = -ENXIO;
		goto out_unset_drv;
	}	
	DBGLEVEL1(ftnandc024_dbg("total oobsize: %d\n", data->mtd.oobsize));
	
	switch(data->mtd.oobsize){
	case 4:
	case 8:
	case 16:
	case 32:
		data->spare = data->mtd.oobsize;
		DBGLEVEL1(ftnandc024_dbg("oobsize(page mode): %02d\n", data->mtd.oobsize));
		break;
	default:
		data->mtd.oobsize = 4;
		data->spare = data->mtd.oobsize;
		DBGLEVEL1(
			ftnandc024_dbg("Warning: Unknown spare setting %d, use default oobsize(page mode): 4\n"
			, data->mtd.oobsize));
		break;
	}
	
	if (data->useecc > 0) {
		DBGLEVEL1(ftnandc024_dbg("ECC correction bits: %d\n", data->useecc));
		writel(0x01010101, data->io_base + ECC_THRES_BITREG1);
		writel(0x01010101, data->io_base + ECC_THRES_BITREG2);
		val = (data->useecc - 1) | ((data->useecc - 1) << 8) | 
			((data->useecc - 1) << 16) | ((data->useecc - 1) << 24);
		writel(val, data->io_base + ECC_CORRECT_BITREG1);
		writel(val, data->io_base + ECC_CORRECT_BITREG2);

		val = readl(data->io_base + ECC_CONTROL);
		val &= ~ECC_BASE;
		if (data->eccbasft > 9)
			val |= ECC_BASE;
		val |= (ECC_EN(0xFF) | ECC_ERR_MASK(0xFF));
		writel(val, data->io_base + ECC_CONTROL);
		writel(ECC_INTR_THRES_HIT | ECC_INTR_CORRECT_FAIL, data->io_base + ECC_INTR_EN);
	} else {
		DBGLEVEL1(ftnandc024_dbg("ECC disabled\n"));
		writel(0, data->io_base + ECC_THRES_BITREG1);
		writel(0, data->io_base + ECC_THRES_BITREG2);
		writel(0, data->io_base + ECC_CORRECT_BITREG1);
		writel(0, data->io_base + ECC_CORRECT_BITREG2);

		val = readl(data->io_base + ECC_CONTROL);
		val &= ~ECC_BASE;
		val &= ~(ECC_EN(0xFF) | ECC_ERR_MASK(0xFF));
		val |= ECC_NO_PARITY;
		writel(val, data->io_base + ECC_CONTROL);
	}

	// Enable the Status Check Intr
	val = readl(data->io_base + INTR_ENABLE);
	val &= ~INTR_ENABLE_STS_CHECK_EN(0xff);
	val |= INTR_ENABLE_STS_CHECK_EN(0xff);
	writel(val, data->io_base + INTR_ENABLE);

	// Setting the ecc capability & threshold for spare
	writel(0x01010101, data->io_base + ECC_THRES_BITREG1);
	writel(0x01010101, data->io_base + ECC_THRES_BITREG2);
	val = (data->useecc_spare-1) | ((data->useecc_spare-1) << 8) |
		((data->useecc_spare-1) << 16) | ((data->useecc_spare-1) << 24);
	writel(val, data->io_base + ECC_CORRECT_BIT_FOR_SPARE_REG1);  
	writel(val, data->io_base + ECC_CORRECT_BIT_FOR_SPARE_REG2);  

	DBGLEVEL2(ftnandc024_dbg("struct nand_buffers size:%d\n", sizeof(struct nand_buffers)));
	data->chip.buffers = kmalloc(sizeof(struct nand_buffers), GFP_KERNEL);
	if (!data->chip.buffers) {
		dev_err(&pdev->dev, "failed to allocate chip buffers.\n");
		res = -ENOMEM;
		goto out_unset_drv;
	}
	else if((sizeof(struct nand_buffers) <  data->mtd.writesize)) {
		dev_err(&pdev->dev, "Please adjust the NAND_MAX_OOBSIZE & NAND_MAX_PAGESIZE\n");
		dev_err(&pdev->dev, "Flash Page size:%d, but NAND_MAX_PAGESIZE is %d\n", data->mtd.writesize, NAND_MAX_PAGESIZE);
		res = -ENOMEM;
		goto out_unset_drv;
	}

	data->chip.ecc.mode = NAND_ECC_HW;
	data->chip.ecc.size = data->mtd.writesize;
	data->chip.ecc.bytes = 0;
	data->chip.ecc.read_page = ftnandc024_nand_read_page;
	data->chip.ecc.write_page = ftnandc024_nand_write_page_lowlevel;
	data->chip.ecc.read_oob = ftnandc024_nand_read_oob_std;
	data->chip.ecc.write_oob = ftnandc024_nand_write_oob_std;
	data->chip.ecc.read_page_raw = ftnandc024_nand_read_page;
	if (data->large_page) {
		data->read_page = ftnandc024_nand_read_page_lp;
		data->write_page = ftnandc024_nand_write_page_lp;
		data->read_oob = ftnandc024_nand_read_oob_lp;
		data->write_oob = ftnandc024_nand_write_oob_lp;
	} else {
		data->read_page = ftnandc024_nand_read_page_sp;
		data->write_page = ftnandc024_nand_write_page_sp;
		data->read_oob = ftnandc024_nand_read_oob_sp;
		data->write_oob = ftnandc024_nand_write_oob_sp;
	}

#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	data->dma_buf = dma_alloc_coherent(&pdev->dev, data->mtd.writesize,
					   &data->dmaaddr, GFP_KERNEL);
	if (!data->dma_buf) {
		res = -ENOMEM;
		goto out_free_dma;
	}
	
	data->slave.id = 0;
	data->slave.channels = NAND_USE_AHBDMA_CHANNEL;
	dma_cap_zero(mask);
	dma_cap_set(DMA_SLAVE, mask);
	dma_chan = dma_request_channel(mask, ftdmac020_chan_filter, &data->slave);
	if (!dma_chan) {
		dev_err(&pdev->dev, "Failed to allocate DMA channel\n");
		res = -EBUSY;
		goto err_request;
	}
	data->dma_chan = dma_chan;
	init_waitqueue_head(&data->dma_wait_queue);
	
	printk("Transfer: DMA\n");
#else
	printk("Transfer: PIO\n");
#endif

	nand_scan_tail(&data->mtd);

	/*----------------------------------------------------------
	 * ONFI synch mode means High Speed. If fails to change to
	 * Synch mode, then use flash as Async mode(Normal speed) and
	 * use LEGACY_LARGE fix flow.
	 */
	if (type == ONFI2) {
		if (ftnandc024_onfi_sync(&data->mtd) == 0) {
			data->flash_type = ONFI2;
			ftnandc024_calc_timing(&data->mtd);
			ftnandc024_calibrate_dqs_delay(&data->mtd, ONFI2);
		}
		else{
			data->flash_type = LEGACY_FLASH;
		}
	}

	// Toggle & ONFI flash has set the proper timing before READ ID.
	// We don't do that twice.	
	if(data->flash_type == LEGACY_FLASH) {
		ftnandc024_calc_timing(&data->mtd);
	}
#if defined(CONFIG_FTNANDC024_SAMSUNG_K9GCGY8S0A)
	else if(data->flash_type == TOGGLE2)
		ftnandc024_warmup_cycle(&data->mtd, 0, 2);
#elif defined(CONFIG_FTNANDC024_TOSHIBA_TH58TEG7DCJBA4C)
		ftnandc024_warmup_cycle(&data->mtd, 3, 3);
#endif
	
	#ifdef CONFIG_MTD_CMDLINE_PARTS
	num_partitions = parse_mtd_partitions(&data->mtd, part_probes, &partitions, 0);
	#endif
	if (num_partitions <= 0) {
		partitions = ftnandc024_partition_info;
		num_partitions = ARRAY_SIZE(ftnandc024_partition_info);
	}
	//res = add_mtd_partitions(&data->mtd, partitions, num_partitions);
	res = mtd_device_parse_register(&data->mtd, NULL, 0, partitions, 
				num_partitions);

	if (!res)
		return res;

	nand_release(&data->mtd);
#ifdef CONFIG_FTNANDC024_USE_AHBDMA
err_request:

out_free_dma:
	kfree(data->chip.buffers);
#endif
out_unset_drv:
	platform_set_drvdata(pdev, NULL);
out_no_ior:
#ifndef CONFIG_FTNANDC024_USE_AHBDMA
	iounmap(data->chip.IO_ADDR_R);
#endif
	iounmap(data->io_base);
out_free_data:
	kfree(data);
out:
	return res;
}

/*
 * Remove a NAND device.
 */
static int __exit ftnandc024_nand_remove(struct platform_device *pdev)
{
	struct ftnandc024_nand_data *data = platform_get_drvdata(pdev);
#ifdef CONFIG_FTNANDC024_USE_AHBDMA
	struct dma_chan *chan = data->dma_chan;
	dma_free_coherent(&pdev->dev, data->mtd.writesize, data->dma_buf, data->dmaaddr);
	chan->device->device_control(chan, DMA_TERMINATE_ALL, 0);	
	dma_release_channel(chan);
#else
	iounmap(data->chip.IO_ADDR_R);
#endif
	nand_release(&data->mtd);
	iounmap(data->io_base);
	kfree(data->chip.buffers);
	kfree(data);

	return 0;
}

static void ftnandc024_release(struct device *dev)
{
}

static u64 ftnandc024_dmamask = DMA_BIT_MASK(32);

static struct platform_device ftnandc024_device = {
	.name = "ftnandc024_nand",
	.id = -1,
	.num_resources = ARRAY_SIZE(ftnandc024_resource),
	.resource = ftnandc024_resource,
	.dev = {
		.dma_mask = &ftnandc024_dmamask,
		.coherent_dma_mask = DMA_BIT_MASK(32),
		.release = ftnandc024_release,
	},
};

static struct platform_driver ftnandc024_nand_driver __refdata = {
	.probe  = ftnandc024_nand_probe,
	.remove = __exit_p(ftnandc024_nand_remove),
	.driver = {
		   .name = "ftnandc024_nand",
		   .owner = THIS_MODULE,
	},
};

static int __init ftnandc024_nand_init(void)
{
	int ret = 0;

	if (platform_device_register(&ftnandc024_device)) {
		printk(KERN_ERR "device register failed\n");
		ret = -ENODEV;
	}
	if (platform_driver_register(&ftnandc024_nand_driver)) {
		printk(KERN_ERR "driver register failed\n");
		ret = -ENODEV;
	}
	return ret;
}

static void __exit ftnandc024_nand_exit(void)
{
	platform_driver_unregister(&ftnandc024_nand_driver);
	platform_device_unregister(&ftnandc024_device);
}

module_init(ftnandc024_nand_init);
module_exit(ftnandc024_nand_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Yeh");
MODULE_DESCRIPTION("FTNANDC024 V2.0 NAND driver");
MODULE_ALIAS("platform:ftnandc024_nand");
